package com.briup.pts.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

// 切面类：需要在类上添加@Apect注解，切面类需要被创建对象
@Slf4j
@Aspect
@Component
public class TestAspect {
    /*
    切面类：就是对目标类实行增强的代码所在类
    代理类：指定是根据切面类和被代理类对象 以及被代理类接口 / 被代理类本身
           而动态生成的类
    被代理类：需要被代理的类
    目标方法：指定的是需要被增强的方法
    增强/通知：某一个具体增强 目标类/方法 而写的方法，
        通常根据执行位置而分为：
            前置通知（目标方法执行前执行）、
            后置通知（目标方法执行完毕后执行）、
            返回后通知（目标方法返回值执行后执行，比后置通知先执行，
            与异常后通知互斥）、
            异常后通知（目标方法出现异常后执行，比后置通知先执行，
            与返回后通知互斥）、
            环绕通知（指可以在方法中，自由控制目标方法在什么时候执行，
            并且只有控制在什么时候对目标方法执行什么样的增强）
     连接点：所有可能被增强的方法，都叫做连接点
     切入点：具体指定哪些方法需要被执行，可以通过execution表达式进行指定
     */

    // 代理Controller层中的所有方法
    /*
     通过注解@Pointcut能够指定切入点是哪些
     execution表达式：
     * com.briup.pts..controller..*(..) 表示为com.briup.pts包下的
     任意结构中的controller包内的任意类，对该类中的任意名字的方法进行代理，
     该方法的返回值以及参数列表并没有做限制
     */
    @Pointcut("execution(* com.briup.pts..controller..*(..))&&" +
    "@annotation(com.briup.pts.aop.TestAop)")
    public void pointcut() {
    }

    // 编写通知，通过@Before等注解标注本方法是前置通知还是后置通知...
    // 编写通知时，必须指定该通知的增强目标，即必须指定切入点
    @Before("pointcut()")
    public void before() {
        log.info("这是前置通知");
    }

    @After("pointcut()")
    public void after() {
        log.info("这是后置通知");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        log.info("出现异常了");
    }

    @AfterReturning("pointcut()")
    public void afterReturning() {
        log.info("方法执行完毕，数据返回完毕");
    }

    // 环绕通知，环绕通知必须存在返回值，以及参数
    // 环绕通知方法的参数类型是固定的，为ProceedingJoinPoint
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        // 通过方法的连接点，获取被代理方法的对象
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        log.info("方法执行前，当前准备执行{}方法", method.getName());
        try {
            // 执行被代理方法
            return pjp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}